Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamically Render Download Contents in ASP.NET 2.0

0.00/5 (No votes)
28 Dec 2006 1  
This article illustrates methodologies for creating download buttons/links dynamically, and provides a DownloadAssistant class as a handy utility.

Introduction

File download is a common functionality that almost every website has to provide in order to distribute contents that can not be presented as regular HTML web pages due to particular content types or large file sizes. Some downloads are available to all users who surf the site, while others are only available to certain users/groups who are authorized to download. A download is generally triggered by a user’s click on either a button or a hyperlink. If the download is restricted to certain users/groups, the users have to be authenticated before the download becomes available. At design time on an ASP.NET page, the button/link would be a Button server control or a LinkButton server control that can be programmed with.

A download can be simply implemented using an URL that directly points to the file located within your website, like the link at the beginning of this article for downloading the demo project. This is fine for public access, but not easy to limit access to certain users/groups with an authentication process. Another implementation is to render the contents through HTTP responses that deliver the contents from a location that can only be reached by your code. This gives you the flexibility to control the user's access. In this article, I would like to demonstrate the methodologies of creating download buttons and links on the fly, using the second implementation, for anonymous access as well as for restricted accesses that require user authentication. The code consists of a download utility class, DownloadAssistant, which simplifies the tasks of building buttons/links and rendering contents, and an ASP.NET page that utilizes the class to present the download buttons/links to web users. The methodologies presented here are based on my own coding practices in several projects I have worked on. You may need to modify the class accordingly to meet your own business needs.

DownloadAssistant Class

The DownloadAssistant class basically does three things: wires up the download functionality to a Button server control or LinkButton server control that is placed on an ASP.NET page at design time; creates download buttons/links dynamically on the fly and presents them through a Panel server control on an ASP.NET page; and renders file contents from a specified file path. The entire code for this class is shown below:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Collections;
 
/// <summary>
/// A class that helps to build download buttons/links on the fly
/// </summary>
static public class DownloadAssistant
{
    static public void DownloadFile(string filePath)
    {
        try
        {
            HttpContext.Current.Response.ContentType = 
                        "application/octet-stream";
            HttpContext.Current.Response.AddHeader("Content-Disposition", 
              "attachment; filename=" + System.IO.Path.GetFileName(filePath));
            HttpContext.Current.Response.Clear();
            HttpContext.Current.Response.WriteFile(filePath);
            HttpContext.Current.Response.End();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
 
    //overloaded method as the Event handler for Button and LinkButton
    static private void DownloadFile(object sender, CommandEventArgs e)
    {
        DownloadFile(e.CommandArgument.ToString());
    }
    //create a download link by passing in a linkbutton
    static public void WireUpDownloadLink(LinkButton linkButton, 
           string linkText, string fileFullPath)
    {
        try
        {
            //set properties
            linkButton.Text = linkText;
            linkButton.CommandArgument = fileFullPath;
            //wire event
            linkButton.Command += new CommandEventHandler(DownloadFile);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    //create a download using a button
    static public void WireUpDownloadButton(Button button, 
           string buttonText, string fileFullPath)
    {
        try
        {
            //set properties
            button.Text = buttonText;
            button.CommandArgument = fileFullPath;
            //wire event
            button.Command += new CommandEventHandler(DownloadFile);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    //Create a group of LinkButtons 
    //and place them in a panel control for downloads
    static public void CreateFileDownloadLinks(Panel pnlDownloadContainer, 
           ArrayList linkText_FilePathPairs, string linkSeparator)
    {
        try
        {
            //loop through the linkTextFilePathPairs 
            //to create LinkButtons and add them to panel
            int i=0, Count=linkText_FilePathPairs.Count;
            foreach (ListItem itm in linkText_FilePathPairs)
            {
                //declare a LinkButton
                LinkButton lnk = new LinkButton();
                //set properties
                lnk.Text = itm.Text;
                lnk.CommandArgument = itm.Value;//file path
                //wire event
                lnk.Command += new CommandEventHandler(DownloadFile);
                //add the created control to container
                pnlDownloadContainer.Controls.Add(lnk);
                if (Count > 1 && i<Count-1)
                //add a separator at the end
                {
                    pnlDownloadContainer.Controls.Add(
                      new System.Web.UI.LiteralControl(linkSeparator));
                }
                ++i;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    //create a group of Buttons and place them in a panel control for downloads
    static public void CreateFileDownloadButtons(Panel pnlDownloadContainer, 
           ArrayList buttonText_FilePathPairs, string buttonSeparator)
    {
        try
        {
            //loop through the buttonText_FilePathPairs 
            //to create LinkButtons and add them to panel
            int i = 0, Count = buttonText_FilePathPairs.Count;
            foreach (ListItem itm in buttonText_FilePathPairs)
            {
                //declare a button
                Button btn = new Button();
                //set properties
                btn.Text = itm.Text;
                btn.CommandArgument = itm.Value;//file path
                //wire event
                btn.Command += new CommandEventHandler(DownloadFile);
                //add the created control to container
                pnlDownloadContainer.Controls.Add(btn);
                if (Count > 1 && i < Count - 1)//add a separator at the end
                {
                    pnlDownloadContainer.Controls.Add(
                      new System.Web.UI.LiteralControl(buttonSeparator));
                }
                ++i;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

The method DownloadFile(string filePath) can be called to render file contents by passing a specified file path into it. The code simply executes HTTP responses to deliver the file contents to web users. However, please pay a little attention to its overloaded method DownloadFile(object sender, CommandEventArgs e). This is particularly important since it is the event handler to be wired to an existing or dynamically created Button/LinkButton, that will fire upon a click. The file path for downloading contents is embedded in the parameter CommandEventArgs e. It will not be possible to download a file without the event handler.

WireUpDownloadButton(Button button, string buttonText, string fileFullPath), as its name implies, is called to wire up a download button by passing an existing Button server control on an ASP.NET page into the method call. Inside the method, the properties Button.Text and Button.CommandArgument are set based on the parameters buttonText and fileFullPath passed in, and then the DownloadFile event is wired to the Button. The WireUpDownloadLink(LinkButton linkButton, string linkText, string fileFullPath) method does exactly the same things with a LinkButton server control instead of a Button server control.

The CreateFileDownloadLinks(Panel pnlDownloadContainer, ArrayList linkText_FilePathPairs, string linkSeparator) method dynamically creates a series of LinkButton server controls on the fly, and adds them to a Panel’s Controls collection so that they can be displayed on an ASP.NET page. The second parameter linkText_FilePathPairs is the type of ArrayList that holds link text and file path data pairs which are used to set the LinkButton’s properties LinkButton.Text and LinkButton.CommandArgument. The method loops through the ArrayList to set properties, wire the DownloadFile event, and then add the created LinkButtons to an existing Panel control that is passed in as a parameter from a calling ASP.NET page. The last parameter, linkSeparator, could be a literal string such as “<br />”, “ | “, or any other character that is used to separate a series of buttons or links created. Similarly, the method CreateFileDownloadButtons(Panel pnlDownloadContainer, ArrayList buttonText_FilePathPairs, string buttonSeparator) dynamically creates a series of Button server controls on the fly.

Call the Methods

A demo application utilizing the DownloadAssistant class is available for download. In Visual Studio 2005, right click on Default.aspx, and select View in Browser; you’ll see that a button with the text “Download File1” and a hyperlink with the text “Download File 2” are displayed on the page. Clicking on the button or link will start the download. A note in red indicates that no authentication is done at this time and only public downloads are available.

Sample image

Click on the Login button to authenticate the current user with the username and password provided. As a result, more download buttons and links are created on the fly and become available for this user.

Sample image

Looking at this page in Visual Studio 2005, three server controls are particularly of our interest, and are placed on the page at design time. Button1, a Button server control, is rendered as an HTML button with the text “Download File 1” at runtime. LinkButton1, a LinkButton server control, is rendered as a hyperlink with the text “Download File 2” at runtime. pnlDownloads, a Panel server control is used as a placeholder for all dynamically created download buttons/links at runtime.

Let’s take a look at the code on the ASP.NET page. In the Page_Load event, the first two method calls are:

DownloadAssistant.WireUpDownloadButton(Button1, "Download File1", 
                  Server.MapPath("~/Files/File1.txt"));
DownloadAssistant.WireUpDownloadLink(LinkButton1, "Download File2", 
                  Server.MapPath("~/Files/File2.txt"));

This code sets the properties concerning display text and file path for Button1 and LinkButton1, and then wires up the DownloadFile event to them. No authentication is performed here. Therefore, the two downloads are available to anonymous users. It is important to indicate that the files (File1.text and File2.text) for download are located in the subdirectory “/Files/” directly under the application root. This is not a good practice since the files are directly accessible through HTTP request using an URL like http://www.somesite.com/DownloadDemo/Files/File1.txt. However, for the convenience of the demo, it is acceptable. Here, Server.MapPath("~/Files/File1.txt") is used to get the physical path of a file. In a real world application, all files should be stored at a location above your web root directory, or on a different physical drive, so that they are only reachable by executing your download code upon clicking a button/link that your page provides. For example, you may place the files in the directory C:\Files\ which is above the default web root directory, c:\InetPub\wwwroot\. In this case, Server.MapPath("~/Files/File1.txt") in the above method call should be replaced by the physical path of “C:\Files\File1.txt”.

The next line of code is a “if” block if (Request.IsAuthenticated){} that checks the status of user authentication (Forms Authentication). If false, do not execute the code in the block; otherwise, dynamically create more download buttons and links and add them into the Panel control that has been placed on the page at design time. As you can see, the parameter ArrayList TextAndPathPairs that contains button/link texts and file paths is hard-coded with five items in it. In a real world application, you'll probably retrieve this data from a database or any other data source based on who logs in so that the user will only be able to download the files he is entitled to. For example, you may show the logged-in user downloadable products he has bought, or reports he has permission to view, etc. Again, all files for download should be located at a location above your web root directory. The action of dynamically creating download buttons and links is easily accomplished by calling the CreateFileDownloadButtons and CreateFileDownloadLinks methods, as shown here:

DownloadAssistant.CreateFileDownloadButtons(pnlDownloads, TextAndPathPairs, "<br>");
DownloadAssistant.CreateFileDownloadLinks(pnlDownloads, TextAndPathPairs, " | ");

The first method call above creates download buttons separated by an HTML line break “<br>”, and the second creates download links separated by a " | " character. The Panel server control, pnlDownloads, is passed into the method calls in order to display the buttons/links at a specified location on the page.

You may have noticed that in the Page_Load event, all code pertaining to creating download buttons/links is outside the if (!Page.IsPostBack){} code block. This means that the code is executed on every postback. Why do we do that? The reason is that for event handlers wired to a server control at runtime (wired to Button1 and LinkButton1) or dynamically created controls at runtime (Buttons and LinksButtons created after successful authentication), their parent ASP.NET page does not automatically “remember” them, and therefore they have to be re-created on every postback. For this reason, this code can also reside in the Page_Init event instead of in the Page_Load event.

The rest of the code related to the Login and Logout buttons on this page just simulates the login and logout processes by calling the methods in the .NET FormsAuthentication class. No user’s credentials are actually verified, which otherwise should be done in a real business application by checking credentials against a user database.

Conclusion

In this article, I presented a DownloadAssistant class as well as a demo ASP.NET page that utilizes the class to illustrate how an event handler can be wired to a download button/link at runtime, and furthermore, how download buttons/links can be dynamically created on the fly in order to satisfy a common business requirement which restricts downloads by showing available download buttons/links based on who logs in. Meanwhile, the DownloadAssistant class is a handy utility that helps to easily build download buttons/links as well as render contents, that you may find useful. The code here is relatively primitive, and may require some polish for your own use. Your feedback is highly appreciated.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here